package zaplog

import (
	"context"
	"fmt"
	"os"
	"sync"
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"go.elastic.co/apm/module/apmzap"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"

	"gitee.com/git-lz/twelve/common/config"
	"gitee.com/git-lz/twelve/common/consts"
	"gitee.com/git-lz/twelve/common/xtrace"
)

var (
	zapLogger     *ZapLog
	zapWarnLogger *ZapLog
	zapAlertLog   *ZapLog
	MysqlLogger   *zap.Logger
)

var once = &sync.Once{}

func Init() {
	once.Do(func() {
		zapLogger = NewZapLogger(getZapCore(), 1)
		zapWarnLogger = NewZapLogger(getWarnZapCore(), 1)
		zapAlertLog = NewZapLogger(getWarnZapCore(), 3)
		MysqlLogger = zap.New(getMysqlZapCore(), zap.AddCaller(), zap.AddCallerSkip(1))
		fmt.Println("zapLogger init success!")
	})
}

type ZapLog struct {
	Log *zap.Logger
}

func NewZapLogger(core zapcore.Core, skip int) *ZapLog {
	logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(skip))
	_, _ = zap.RedirectStdLogAt(logger, zap.DebugLevel)

	name := config.Viper.GetString("app.name")
	if name == "" {
		name = "feige-default"
	}
	logger = logger.Named(name)

	// 替换全局logger
	zap.ReplaceGlobals(logger)

	return &ZapLog{
		Log: logger,
	}
}

func (l *ZapLog) AddTrace(ctx context.Context) *zap.Logger {
	return l.Log.With(zap.String("trace_id", xtrace.TraceIdFromContext(ctx)))
}

func Info(ctx context.Context, msg string, start time.Time, fields ...zapcore.Field) {
	fields = append(fields, zap.Duration(consts.DLTagCost, time.Since(start)))
	zapLogger.AddTrace(ctx).Info(msg, fields...)
}

func Warn(ctx context.Context, msg string, start time.Time, fields ...zapcore.Field) {
	fields = append(fields, zap.Duration(consts.DLTagCost, time.Since(start)))
	zapWarnLogger.AddTrace(ctx).Warn(msg, fields...)
}

func Alertf(ctx context.Context, msg string, start time.Time, fields ...zapcore.Field) {
	fields = append(fields, zap.Duration(consts.DLTagCost, time.Since(start)))
	zapAlertLog.AddTrace(ctx).Warn(msg, fields...)
}

func Errorf(ctx context.Context, msg string, start time.Time, fields ...zapcore.Field) {
	fields = append(fields, zap.Duration(consts.DLTagCost, time.Since(start)))
	zapWarnLogger.AddTrace(ctx).Error(msg, fields...)
}

func getZapCore() zapcore.Core {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.FunctionKey = "func"

	allLogFileName := fmt.Sprintf("%v", config.Viper.GetString("log.prefix"))
	return zapcore.NewTee(getZapCoreBase(allLogFileName, zapcore.InfoLevel), &apmzap.Core{})
}

func getWarnZapCore() zapcore.Core {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.FunctionKey = "func"

	errLogFileName := fmt.Sprintf("%v", config.Viper.GetString("log.error.prefix"))
	return zapcore.NewTee(getZapCoreBase(errLogFileName, zapcore.WarnLevel), &apmzap.Core{})
}

func getMysqlZapCore() zapcore.Core {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.FunctionKey = "func"

	mysqlLogFileName := fmt.Sprintf("%v", config.Viper.GetString("log.mysql.prefix"))

	return zapcore.NewTee(getZapCoreBase(mysqlLogFileName, zapcore.DebugLevel), &apmzap.Core{})
}

func getRedisZapCore() zapcore.Core {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.FunctionKey = "func"

	redisLogFileName := fmt.Sprintf("%v", config.Viper.GetString("log.redis.prefix"))

	return zapcore.NewTee(getZapCoreBase(redisLogFileName, zapcore.DebugLevel), &apmzap.Core{})
}

func getCronZapCore() zapcore.Core {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.FunctionKey = "func"

	redisLogFileName := fmt.Sprintf("%v", config.Viper.GetString("log.cron.prefix"))

	return zapcore.NewTee(getZapCoreBase(redisLogFileName, zapcore.DebugLevel), &apmzap.Core{})
}

func getGrpcZapCore() zapcore.Core {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.FunctionKey = "func"

	redisLogFileName := fmt.Sprintf("%v", config.Viper.GetString("log.grpc.prefix"))

	return zapcore.NewTee(getZapCoreBase(redisLogFileName, zapcore.InfoLevel), &apmzap.Core{})
}

func getZapCoreBase(filename string, level zapcore.Level) zapcore.Core {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.FunctionKey = "func"

	if config.Viper.GetBool("log.levelColor") {
		encoderConfig.EncodeLevel = zapcore.LowercaseColorLevelEncoder
	}

	logRotate, err := rotatelogs.New(
		filename+".%Y%m%d%H",
		rotatelogs.WithMaxAge(config.Viper.GetDuration("log.maxAgeDays")*24*time.Hour),
		rotatelogs.WithRotationTime(time.Hour),
	)
	if err != nil {
		panic(fmt.Errorf("log init err=(%w)", err))
	}

	var syncers []zapcore.WriteSyncer
	syncers = append(syncers, zapcore.AddSync(logRotate))

	if config.Viper.GetBool("log.stdout") {
		syncers = append(syncers, getStdoutSyncer())
	}

	if config.Viper.GetString("log.format") == "json" {
		return zapcore.NewCore(zapcore.NewJSONEncoder(encoderConfig), zapcore.NewMultiWriteSyncer(syncers...), level)
	} else {
		return zapcore.NewCore(zapcore.NewConsoleEncoder(encoderConfig), zapcore.NewMultiWriteSyncer(syncers...), level)
	}
}

func getStdoutSyncer() zapcore.WriteSyncer {
	return zapcore.AddSync(os.Stdout)
}

func testLog() {
	start := time.Now()

	Info(context.Background(), "hello world!", start, zap.String("hh", "ee"))
	Warn(context.Background(), "hello world!", start, zap.String("hh", "ee"))
	Errorf(context.Background(), "hello world!", start, zap.String("hh", "ee"))
	MysqlLogger.Info("hello world!", zap.String("hh", "ee"))
}
